//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.IdentityModel.Tokens;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Xml;

namespace Shp.Security.BrokeredReceiver
{
    /// <summary>
    /// A class that represents a RequestSecurityTokenResponse message per February 2005 WS-Trust
    /// </summary>
    [ComVisible(false)]
    public class RequestSecurityTokenResponse : RequestSecurityTokenBase
    {
        private SecurityToken requestedSecurityToken;
        private SecurityToken requestedProofToken;
        private SecurityToken issuerEntropy;
        private SecurityKeyIdentifierClause requestedAttachedReference;
        private SecurityKeyIdentifierClause requestedUnattachedReference;
        private bool computeKey;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:RequestSecurityTokenResponse"/> class.
        /// </summary>
        public RequestSecurityTokenResponse()
            : this(String.Empty, String.Empty, 0, null, null, null, false)
        {
        }

        /// <summary>
        /// Parameterized constructor
        /// </summary>
        /// <param name="context">The value of the wst:RequestSecurityTokenResponse/@Context attribute</param>
        /// <param name="tokenType">The content of the wst:RequestSecurityTokenResponse/wst:TokenType element</param>
        /// <param name="keySize">The content of the wst:RequestSecurityTokenResponse/wst:KeySize element </param>
        /// <param name="appliesTo">An EndpointReference corresponding to the content of the wst:RequestSecurityTokenResponse/wsp:AppliesTo element</param>
        /// <param name="requestedSecurityToken">The requested security token</param>
        /// <param name="requestedProofToken">The proof token associated with the requested security token</param>
        /// <param name="computeKey">A boolean specifying whether a key value needs to be computed</param>
        public RequestSecurityTokenResponse(string context, 
                                            string tokenType, 
                                            int keySize, 
                                            EndpointAddress appliesTo, 
                                            SecurityToken requestedSecurityToken, 
                                            SecurityToken requestedProofToken, 
                                            bool computeKey ) :
            base(context, tokenType, keySize, appliesTo) // Pass first 4 params to base class
        {
            this.requestedSecurityToken = requestedSecurityToken;
            this.requestedProofToken = requestedProofToken;
            this.computeKey = computeKey;
        }

        // public properties
        /// <summary>
        /// The requested SecurityToken
        /// </summary>
        public SecurityToken RequestedSecurityToken
        {
            get { return requestedSecurityToken; }
            set { requestedSecurityToken = value; }
        }

        /// <summary>
        /// A SecurityToken representing the proof token associated with 
        /// the requested SecurityToken
        /// </summary>
        public SecurityToken RequestedProofToken
        {
            get { return requestedProofToken; }
            set { requestedProofToken = value; }
        }

        /// <summary>
        /// A SecurityKeyIdentifierClause that can be used to refer to the requested 
        /// SecurityToken when that token is present in messages
        /// </summary>
        public SecurityKeyIdentifierClause RequestedAttachedReference
        {
            get { return requestedAttachedReference; }
            set { requestedAttachedReference = value; }
        }

        /// <summary>
        /// A SecurityKeyIdentifierClause that can be used to refer to the requested 
        /// SecurityToken when that token is present in messages
        /// </summary>
        public SecurityKeyIdentifierClause RequestedUnattachedReference
        {
            get { return requestedUnattachedReference; }
            set { requestedUnattachedReference = value; }
        }

        /// <summary>
        /// The SecurityToken representing entropy provided by the issuer
        /// Null if the issuer did not provide entropy
        /// </summary>
        public SecurityToken IssuerEntropy
        {
            get { return issuerEntropy; }
            set { issuerEntropy = value; }
        }

        /// <summary>
        /// Indicates whether a key needs to be computed (typically from the combination of issuer and requestor entropy)
        /// </summary>
        public bool ComputeKey
        {
            get { return computeKey; }
            set { computeKey = value; }
        }

        // public methods
        /// <summary>
        /// Static method that computes a combined key from issue and requestor entropy using PSHA1 per WS-Trust
        /// </summary>
        /// <param name="requestorEntropy">Entropy provided by the requestor</param>
        /// <param name="issuerEntropy">Entropy provided by the issuer</param>
        /// <param name="keySize">Size of required key, in bits</param>
        /// <returns>Array of bytes containing key material</returns>
        static public byte[] ComputeCombinedKey(byte[] requestorEntropy, byte[] issuerEntropy, int keySize)
        {
            KeyedHashAlgorithm keyedHash = new HMACSHA1(requestorEntropy, true);

            byte[] key = new byte[keySize / 8]; // Final key
            byte[] entropy = issuerEntropy; // A(0)
            byte[] buffer = new byte[keyedHash.HashSize / 8 + entropy.Length]; // Buffer for A(i) + seed

            for (int i = 0; i < key.Length;)
            {
                // Calculate A(i+1).                
                keyedHash.Initialize();
                entropy = keyedHash.ComputeHash(entropy);

                // Calculate A(i) + seed
                entropy.CopyTo(buffer, 0);
                issuerEntropy.CopyTo(buffer, entropy.Length);
                keyedHash.Initialize();
                byte[] result = keyedHash.ComputeHash(buffer);

                for (int j = 0; j < result.Length; j++)
                {
                    if (i < key.Length)
                    {
                        key[i++] = result[j];
                    }
                    else
                    {
                        break;
                    }
                }
            }

            return key;
        }

        // Methods of BodyWriter        
        /// <summary>
        /// Writes out an XML representation of the instance.
        /// </summary>
        /// <param name="writer">The writer to be used to write out the XML content</param>
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement(Constants.TrustFeb2005.RequestSecurityTokenResponse, Constants.TrustFeb2005.Namespace);

            if (this.TokenType != null && this.TokenType.Length > 0)
            {
                writer.WriteStartElement(Constants.TrustFeb2005.TokenType, Constants.TrustFeb2005.Namespace);
                writer.WriteString(this.TokenType);
                writer.WriteEndElement(); // wst:TokenType
            }

            WSSecurityTokenSerializer ser = new WSSecurityTokenSerializer();

            if (this.requestedSecurityToken != null)
            {
                writer.WriteStartElement(Constants.TrustFeb2005.RequestedSecurityToken, Constants.TrustFeb2005.Namespace);                
                ser.WriteToken(writer, requestedSecurityToken);                    
                writer.WriteEndElement(); // wst:RequestedSecurityToken
            }

            if ( this.requestedAttachedReference != null )
            {
                writer.WriteStartElement(Constants.TrustFeb2005.RequestedAttachedReference, Constants.TrustFeb2005.Namespace);
                ser.WriteKeyIdentifierClause ( writer, this.requestedAttachedReference );
                writer.WriteEndElement(); 
            }

            if ( this.requestedUnattachedReference != null )
            {
                writer.WriteStartElement(Constants.TrustFeb2005.RequestedUnattachedReference, Constants.TrustFeb2005.Namespace);
                ser.WriteKeyIdentifierClause ( writer, this.requestedUnattachedReference );
                writer.WriteEndElement(); 
            }

            if (this.AppliesTo != null)
            {
                writer.WriteStartElement(Constants.Policy.AppliesTo, Constants.Policy.NamespaceUri);
                this.AppliesTo.WriteTo(AddressingVersion.WSAddressing10, writer);
                writer.WriteEndElement(); 
            }

            if (this.requestedProofToken != null)
            {
                writer.WriteStartElement(Constants.TrustFeb2005.RequestedProofToken, Constants.TrustFeb2005.Namespace);
                ser.WriteToken(writer, requestedProofToken);
                writer.WriteEndElement(); 
            }

            if(this.issuerEntropy != null && this.computeKey ) 
            {
                writer.WriteStartElement(Constants.TrustFeb2005.RequestedProofToken, Constants.TrustFeb2005.Namespace);
                writer.WriteStartElement(Constants.TrustFeb2005.ComputedKey, Constants.TrustFeb2005.Namespace);
                writer.WriteValue(Constants.TrustFeb2005.Psha1ComputedKeyUri);
                writer.WriteEndElement(); 
                writer.WriteEndElement(); 

                writer.WriteStartElement(Constants.TrustFeb2005.Entropy, Constants.TrustFeb2005.Namespace);                    
                ser.WriteToken(writer, this.issuerEntropy);
                writer.WriteEndElement();
            }

            writer.WriteEndElement(); 
        }
    }
}
